﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using ArlesInterfaces.Editor;
using ArlesInterfaces.Render;
using Arles.src.controller;
using Arles.src.controller.Commands;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.ComponentModel;
using System.Drawing.Design;
using Arles.src.view.controls;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Arles.src.utils;

namespace Arles.src.model.objects
{
    public class HeightmapObject : ISceneObject
    {
        #region Private Members
        private Texture2D heightmap;
        private string heightmap_filename;
        private Texture2D texture;
        private string texture_filename;
        private float terrain_scale_x = 4.0f;
        private float terrain_scale_y = 4.0f;
        private float terrain_scale_height = 0.5f;
        private float terrain_width;
        private float terrain_height;
        private int texture_width; //pixels heightmap
        private int texture_height;
        private int current_index = 0;
        private bool enable = false;

        private GraphicsDevice device;
        private ICamera camera;

        #endregion

        #region "Render attributes"

        [StructLayout(LayoutKind.Sequential)]
        public struct SVertexFormat
        {
            Vector3 vertexPosition;
            Vector3 vertexNormal;
            Vector2 vertexTexcoord;

            public static readonly VertexElement[] VertexElements = new VertexElement[]
              {
                 new VertexElement(0, 0, VertexElementFormat.Vector3,
                  VertexElementMethod.Default, VertexElementUsage.Position, 0),
                 new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3,
                  VertexElementMethod.Default, VertexElementUsage.Normal, 0),
                 new VertexElement(0, sizeof(float) * 3 * 2, VertexElementFormat.Vector2,
                  VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
              };

            public SVertexFormat(Vector3 pos, Vector3 normal, Vector2 texcoord)
            {
                vertexPosition = pos;
                vertexNormal = normal;
                vertexTexcoord = texcoord;
            }

            //Public methods for accessing the components of the custom vertex.
            public Vector3 Position { get { return vertexPosition; } set { vertexPosition = value; } }
            public Vector3 Normal { get { return vertexNormal; } set { vertexNormal = value; } }
            public Vector2 TextureCoordinate { get { return vertexTexcoord; } set { vertexTexcoord = value; } }
        }

        SVertexFormat[] vertices;
        VertexDeclaration quad_vertex_decl;
        BasicEffect effect;

        #endregion

        #region Properties
        [CategoryAttribute("Aspecto"), DescriptionAttribute("Fichero heightmap.")]
        [Editor(typeof(FilteredFileNameEditor), typeof(UITypeEditor))]
        public string HeightmapFile
        {
            get { return heightmap_filename; }
            set
            {
                heightmap_filename = value;
                load();
            }
        }

        [CategoryAttribute("Aspecto"), DescriptionAttribute("Textura terrain.")]
        [Editor(typeof(FilteredFileNameEditor), typeof(UITypeEditor))]
        public string Texture
        {
            set
            {
                if (File.Exists(value))
                {
                    texture_filename = value;
                    //Para device mejor usar directamente "Editor.Instance.Render.Device"
                    texture = Texture2D.FromFile(device, texture_filename);
                }
            }
            get
            {
                return texture_filename;
            }
        }

        [CategoryAttribute("Aspecto"), DescriptionAttribute("Escala en X.")]
        public float TerrainScaleX
        {
            set
            {
                terrain_scale_x = value;
                loadData();
            }
            get
            {
                return terrain_scale_x;
            }
        }

        [CategoryAttribute("Aspecto"), DescriptionAttribute("Escala en Z.")]
        public float TerrainScaleY
        {
            set
            {
                terrain_scale_y = value;
                loadData();
            }
            get
            {
                return terrain_scale_y;
            }
        }

        [CategoryAttribute("Aspecto"), DescriptionAttribute("Escala en Y (altura).")]
        public float TerrainScaleHeight
        {
            set
            {
                terrain_scale_height = value;
                loadData();
            }
            get
            {
                return terrain_scale_height;
            }
        }

        [Browsable(false)]
        public Int64 Tag { get; set; }
        #endregion

        #region Import File
        private void loadData()
        {
          enable = false;
          if (heightmap == null)
          {
            texture_width = 256;
            texture_height = 256;
          }
          else 
          {
            texture_width = heightmap.Width;
            texture_height = heightmap.Height;
          }
          terrain_width = texture_width * terrain_scale_x;
          terrain_height = texture_height * terrain_scale_y;
          uint[] data = new uint[texture_width * texture_height];
          if (heightmap != null)
          {
            heightmap.GetData<uint>(data);
          }
          Vector3[] positions = new Vector3[texture_width * texture_height];
          for (int y = 0; y < texture_height; ++y)
          {
            for (int x = 0; x < texture_width; ++x)
            {
              Vector3 pos;
              //El centro del mapa es el 0,0,0
              pos.X = (x - ((texture_width - 1) / 2.0f)) * terrain_scale_x;
              pos.Z = (y - ((texture_height - 1) / 2.0f)) * terrain_scale_y;
              //Altura terreno
              int indice = y * (int)texture_width + x;
              pos.Y = (data[indice] & 0x000000ff) * terrain_scale_height;
              //Añadimos posicion a la lista de vertices de la malla
              positions[indice] = pos;
            }
          }

          // Create the individual triangles that make up our terrain.
          current_index = 0;
          vertices = new SVertexFormat[((int)texture_height - 1) * (texture_width - 1) * 6];
          int count_tris = 0;
          for (int y = 0; y < (texture_height - 1); y++)
          {
            for (int x = 0; x < (texture_width - 1); x++)
            {
              if (count_tris == 61076)
                  count_tris = 61076;
              AddVertex(x, y, positions);
              AddVertex(x + 1, y, positions);
              AddVertex(x + 1, y + 1, positions);
              ++count_tris;

              AddVertex(x, y, positions);
              AddVertex(x + 1, y + 1, positions);
              AddVertex(x, y + 1, positions);
              ++count_tris;
            }
          }
          enable = true;
        }

        private void AddVertex(int x, int y, Vector3[] positions)
        {
          float width = (float)texture_width;
          float height = (float)texture_height;
          Vector2 coord;
          coord.X = ((float)x) / width;
          coord.Y = ((float)y) / height;

          int index = y * texture_width + x;
          SVertexFormat vertex = new SVertexFormat(positions[index], Vector3.Zero, coord);
          //VertexPositionTexture vertex = new VertexPositionTexture(positions[index], coord);
          vertices[current_index] = vertex;
          ++current_index;
        }

        public void load()
        {
          enable = false;
          if (heightmap_filename.Length > 0)
          {
            Texture2D heightmap = Texture2D.FromFile(device, heightmap_filename);
            if (heightmap.Width > 300 || heightmap.Height > 300)
            {
              if (System.Windows.Forms.MessageBox.Show("Parece que has escogido una textura demasiado grande, seguro que quieres continuar?", "Textura demasiado grande", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.No)
              {
                heightmap.Dispose();
                enable = true;
                return;
              }
            }
            this.heightmap = heightmap;
          }
          else
          {
            heightmap = null;
          }
          loadData();
        }
        #endregion

        #region Initialize
        public void Initialize()
        {
            device = Editor.Instance.Render.Device;
            camera = Editor.Instance.Render.Camera;

            quad_vertex_decl = new VertexDeclaration(device, SVertexFormat.VertexElements);
            effect = new BasicEffect(device, null);
        }
        #endregion

        #region Update
        public void Update(GameTime gameTime)
        {
        }
        #endregion

        #region Render
        public void Draw(GameTime gameTime)
        {
            if (!enable) return;
            device.VertexDeclaration = quad_vertex_decl;
            effect.World = Matrix.CreateTranslation(Vector3.Zero);
            effect.View = camera.View;
            effect.Projection = camera.Projection;
            if (texture == null)
            {
                effect.TextureEnabled = false;
            }
            else
            {
                effect.TextureEnabled = true;
                effect.Texture = texture;
            }
            effect.Begin();
            effect.Techniques[0].Passes[0].Begin();
            int total_primitives = vertices.Length / 3;
            int count_primitives = 61000;
            int o = total_primitives / count_primitives;
            int offset = 0;
            for (int i = 0; i <= o; ++i)
            {
                if (i == o)
                {
                    count_primitives = total_primitives - (count_primitives * i);
                }
                device.DrawUserPrimitives<SVertexFormat>(PrimitiveType.TriangleList, vertices, offset, count_primitives);
                offset += count_primitives * 3;
            }
            effect.Techniques[0].Passes[0].End();
            effect.End();
        }
        #endregion
    }
}
